/**
 * Covalent Server Code File
 * 
 * File:        interface.c
 * Brief:       BRIEF
 * Author:      Chen Kangbing
 * Email:       ckblau@outlook.com
 * Modified:    2021/10/09 09:28:56
 *
 */


#include "interface.h"

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>


#define GET_PATH_NUM 1
const char *GET_PATH[GET_PATH_NUM] = {
    "/cgi-bin"
};

#define GET_EXT_NUM 4
const char *GET_EXT[GET_EXT_NUM] = {
    "htm",
    "html",
    "js",
    "css"
};

#define POST_PATH_NUM 1
const char *POST_PATH[POST_PATH_NUM] = {
    "/cgi-bin"
};

#define POST_EXT_NUM 2
const char *POST_EXT[POST_EXT_NUM] = {
    "py",
    "cgi"
};

const char *ROOT_DIR = "./webroot";
const char *DEFAULT_FILE = "index.html";

int folder_exist(const char *path) {
    if (!path) return 0;
    DIR *dp;
    if (!(dp = opendir(path))) return 0;
    closedir(dp);
    return 1;
}

int file_exist(const char *path) {
    if (!path) return 0;
    return !access(path, F_OK);
}

void free_path(ReqPathTypeDef *path) {
    if (!path) return;
    if (path->dir) free(path->dir);
    if (path->file) free(path->file);
    if (path->ext) free(path->ext);
    free(path);
}

int split_path(char *path_str, int path_len, ReqPathTypeDef *path) {
    if (!path_str || !path) return 1;
    if (path_str[0] != '/') return 2;
    
    char *eop;
    eop = path_str + path_len;

    char *dir_e;
    dir_e = strrchr(path_str, '/');
    if (!dir_e || (dir_e - path_str) > 256) return 3;
    path->dir_len = dir_e - path_str;
    if (path->dir_len <= 0) {
        path->dir = NULL;
    }
    else {
        path->dir = malloc(path->dir_len + 1);
        if (!path->dir) return 4;
        strncpy(path->dir, path_str, path->dir_len);
        *(path->dir + path->dir_len) = 0;
    }

    char *file_b;
    file_b = dir_e + 1;
    if (file_b >= eop) {
        path->file = NULL;
        path->ext = NULL;
        return 0;
    }
    char *file_e;
    file_e = strrchr(file_b, '.');
    path->file_len = eop - file_b;
    if (path->file_len <= 0) {
        path->file = NULL;
    }
    else {
        path->file = malloc(path->file_len + 1);
        if (!path->file) return 5;
        strncpy(path->file, file_b, path->file_len);
        *(path->file + path->file_len) = 0;
        if (!file_e) {
            path->ext = NULL;
            return 0;
        }
    }
    
    char *ext_b;
    ext_b = file_e + 1;
    if (ext_b >= eop) {
        path->ext = NULL;
        return 0;
    }
    path->ext_len = eop - ext_b;
    path->ext = malloc(path->ext_len + 1);
    if (!path->ext) return 6;
    strncpy(path->ext, ext_b, path->ext_len);
    *(path->ext + path->ext_len) = 0;
    
    return 0;
}

int check_get_path(TransactionTypeDef *ptrans) {
    if (!ptrans) return -1;

    ReqPathTypeDef *path;
    path = calloc(1, sizeof(ReqPathTypeDef));
    if (!path) return 500;

    int rv;
    rv = split_path(ptrans->path, ptrans->path_len, path);
    if (rv) {
        free_path(path);
        return 404;
    }

    // printf("Dir:\t\"%s\"\nFile:\t\"%s\"\nExt:\t\"%s\"\n", path->dir, path->file, path->ext);
    
    int path_flag = 0;
    if (!path->dir) {
        path_flag = 1;
    }
    else {
        for (int i = 0; i < GET_PATH_NUM; ++i) {
            if (!strcmp(path->dir, GET_PATH[i])) {
                path_flag = 1;
                break;
            }
        }
    }
    if (!path_flag) {
        free_path(path);
        return 403;
    }

    int index_flag = 0;
    if (!path->file) index_flag = 1;

    int ext_flag = 0;
    if (!path->ext) {
        if (index_flag) ext_flag = 1;
    }
    else {
        for (int i = 0; i < GET_EXT_NUM; ++i) {
            if (!strcasecmp(path->ext, GET_EXT[i])) {
                ext_flag = 1;
                break;
            }
        }
    }
    if (!ext_flag) {
        free_path(path);
        return 403;
    }

    ptrans->real_path = malloc(path->dir_len + path->file_len + 30);
    char *buf = ptrans->real_path;
    if (path->dir) buf += sprintf(buf, "%s%s/", ROOT_DIR, path->dir);
    else buf += sprintf(buf, "%s/", ROOT_DIR);
    if (index_flag) buf += sprintf(buf, "%s", DEFAULT_FILE);
    else buf += sprintf(buf, "%s", path->file);
    ptrans->real_path_len = buf - ptrans->real_path;

    // printf("Real Path: %s\n", ptrans->real_path);

    if (!file_exist(ptrans->real_path)) {
        free_path(path);
        return 404;
    }

    free_path(path);
    return 0;
}

int fetch_file(TransactionTypeDef *ptrans) {
    if (!ptrans) return -1;

    FILE *pf = fopen(ptrans->real_path, "r");
    if (!pf) return -2;

    fseek(pf, 0, SEEK_END);
    ptrans->rsp_body_size = ftell(pf);
    rewind(pf); 

    ptrans->rsp_body = malloc(ptrans->rsp_body_size + 1);
    if (!ptrans->rsp_body) return -3;
    
    fread(ptrans->rsp_body, sizeof(char), ptrans->rsp_body_size, pf);
    ptrans->rsp_body[ptrans->rsp_body_size] = '\0';

    fclose(pf);
    return 0;
}

int check_post_path(TransactionTypeDef *ptrans) {
    if (!ptrans) return -1;

    ReqPathTypeDef *path;
    path = calloc(1, sizeof(ReqPathTypeDef));
    if (!path) return 500;

    int rv;
    rv = split_path(ptrans->path, ptrans->path_len, path);
    if (rv) {
        free_path(path);
        return 404;
    }

    // printf("Dir:\t\"%s\"\nFile:\t\"%s\"\nExt:\t\"%s\"\n", path->dir, path->file, path->ext);
    
    int path_flag = 0;
    if (!path->dir) {
        path_flag = 1;
    }
    else {
        for (int i = 0; i < POST_PATH_NUM; ++i) {
            if (!strcmp(path->dir, POST_PATH[i])) {
                path_flag = 1;
                break;
            }
        }
    }
    if (!path_flag) {
        free_path(path);
        return 403;
    }

    if (!path->file) {
        free_path(path);
        return 404;
    }

    int ext_flag = 0;
    if (path->ext) {
        for (int i = 0; i < POST_EXT_NUM; ++i) {
            if (!strcasecmp(path->ext, POST_EXT[i])) {
                ext_flag = 1;
                break;
            }
        }
    }
    if (!ext_flag) {
        free_path(path);
        return 403;
    }

    ptrans->real_path = malloc(path->dir_len + path->file_len + 30);
    char *buf = ptrans->real_path;
    if (path->dir) buf += sprintf(buf, "%s%s/", ROOT_DIR, path->dir);
    else buf += sprintf(buf, "%s/", ROOT_DIR);
    buf += sprintf(buf, "%s", path->file);
    ptrans->real_path_len = buf - ptrans->real_path;

    // printf("Real Path: %s\n", ptrans->real_path);

    if (!file_exist(ptrans->real_path)) {
        free_path(path);
        return 404;
    }

    free_path(path);
    return 0;
}

int exec_cgi(TransactionTypeDef *ptrans) {
    if (!ptrans) return -1;

    int input[2], output[2];
    pid_t pid;
    if (pipe(input) < 0) return 0;
    if (pipe(output) < 0) return 0;

    pid = fork();
    if (pid == 0) {
        // printf("hey i m child\n");
        dup2(input[0], STDIN_FILENO);
        // printf("ptrans->real_path: %s\n",ptrans->real_path);
        dup2(output[1], STDOUT_FILENO);
        close(output[0]);
        close(input[1]);
        execl(ptrans->real_path, ptrans->real_path, NULL);
    }
    else if (pid > 0) {
        char *tmp = malloc(1000 + 1);
        int size = 0;

        close(output[1]);
        close(input[0]);
        write(input[1], ptrans->req_body, ptrans->req_body_size);
        close(input[1]);
        size = read(output[0], tmp, 1000);
        close(output[0]);
        // printf("recv: \"%s\"\n", ptrans->rsp_body);

        if (size <= 2) return -2; 

        for (int i = 0; i < size - 1; ++i) {
            if (tmp[i] == '\r' && tmp[i + 1] == '\n') {
                ptrans->rsp_header_size = i;
                ptrans->rsp_header = malloc(ptrans->rsp_header_size + 1);
                if (!ptrans->rsp_header) return -3;
                strncpy(ptrans->rsp_header, tmp, ptrans->rsp_header_size);
                ptrans->rsp_header[ptrans->rsp_header_size] = 0;
            }
        }

        ptrans->rsp_body_size = size - ptrans->rsp_header_size - 2;
        ptrans->rsp_body = malloc(ptrans->rsp_body_size + 1);
        if (!ptrans->rsp_body) return -4;
        strncpy(ptrans->rsp_body, tmp + ptrans->rsp_header_size + 2, ptrans->rsp_body_size);
        ptrans->rsp_body[ptrans->rsp_body_size] = 0;

        free(tmp);
    }
    else {
        return -5;
    }

    return 0;
}

int load_error_page(TransactionTypeDef *ptrans) {
    if (!ptrans) return -1;

    char error_page[300];
    sprintf(error_page, "%s/%d.html", ROOT_DIR, ptrans->status);

    FILE *pf = fopen(error_page, "r");
    if (!pf) return -1;

    fseek(pf, 0, SEEK_END);
    ptrans->rsp_body_size = ftell(pf);
    rewind(pf); 

    ptrans->rsp_body = malloc(ptrans->rsp_body_size + 1);
    if (!ptrans->rsp_body) return -3;
    
    fread(ptrans->rsp_body, sizeof(char), ptrans->rsp_body_size, pf);
    ptrans->rsp_body[ptrans->rsp_body_size] = '\0';

    fclose(pf);
    return 0;
}